﻿// entry point is at the end
var mjsPlayers = new Array();

// Using jresig's Class implementation http://ejohn.org/blog/simple-javascript-inheritance/
(function(){var initializing=false, fnTest=/xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; this.Class = function(){}; Class.extend = function(prop) { var _super = this.prototype; initializing = true; var prototype = new this(); initializing = false; for (var name in prop) { prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn){ return function() { var tmp = this._super; this._super = _super[name]; var ret = fn.apply(this, arguments); this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } function Class() { if ( !initializing && this.init ) this.init.apply(this, arguments); } Class.prototype = prototype; Class.constructor = Class; Class.extend = arguments.callee; return Class;};})();

var MediaJS = Class.extend({
  init: function(box, setOptions){
    this.box = box;

    var vid = document.createElement('video');
    vid.setAttribute('poster', mjs_vocatube_url + box.id + '.poster');
    vid.controls = false;
    vid.setAttribute('preload', 'metadata');
    vid.style.height = '100%';
    vid.style.width = '100%';

    vid.addEventListener('loadedmetadata', this.onLoadedMetadata.context(this),false);

    this.box.appendChild(vid);
    this.media  = vid;

    // Default Options
    this.options = {
      defaultVolume: 0.5, // Will be overridden by localStorage volume if available
      linksHiding: true,
      autoplay: false,
    };

    // Override default options with set options
    if (typeof setOptions == "object") _mjs_.merge(this.options, setOptions);

    this.buildPlayButton(this.options.autoplay); // may need it later, also addressed in its click handler
  },


  buildPlayButton: function(autoplay){
    this.bigPlayButton = _mjs_.createElement("div", { className: "mjs-play-control mjs-big-play-button", innerHTML: "<span></span>" });
    this.box.appendChild(this.bigPlayButton);

    if (autoplay){
      this.bigPlayButton.style.display = 'none';
      this.onBigPlayButtonClick();
    } else {
      this.bigPlayButton.style.display = 'block';
      this.bigPlayButton.addEventListener('click', this.onBigPlayButtonClick.context(this),false);
    }
  },


  onBigPlayButtonClick: function(){
    _mjs_.get(mjs_vocatube_url + this.box.id + ".json", this.onMediaInfoRequest.context(this));
    
    // meanwhile, build and setup controls
    this.bigPlayButton.style.display = 'none';
    this.percentLoaded = 0;

    if (this.options.controlsBelow) {
      _mjs_.addClass(this.box, "mjs-controls-below");
    }

    this.buildController();
    this.showController();

    // Position & show controls when data is loaded
    this.media.addEventListener("loadeddata", this.onLoadedData.context(this), false);

    this.media.addEventListener("play", this.onPlay.context(this), false);
    this.media.addEventListener("pause", this.onPause.context(this), false);
    this.media.addEventListener("ended", this.onEnded.context(this), false);
    this.media.addEventListener('volumechange',this.onVolumeChange.context(this),false);

    this.media.addEventListener('error',this.onError.context(this),false);
    this.media.addEventListener('progress', this.onProgress.context(this), false);

    this.watchBuffer = setInterval(this.updateBufferedTotal.context(this), 33);

    // TODO: check mouse over/out for just video
    this.media.addEventListener('mousemove', this.onMouseMove.context(this), true);
    this.media.addEventListener('mouseout', this.onMouseOut.context(this), true);

    this.media.addEventListener("click", this.onPlayControlClick.context(this), false);
    this.playControl.addEventListener("click", this.onPlayControlClick.context(this), false);

    this.controls.addEventListener('mouseout', this.onMouseOut.context(this), false); // TODO: Chrome wants it for EVERY div :/

    this.progressHolder.addEventListener("mousedown", this.onProgressHolderMouseDown.context(this), false);
    this.progressHolder.addEventListener("mouseup", this.onProgressHolderMouseUp.context(this), false);

    this.setVolume(localStorage.volume || this.options.defaultVolume);

    this.volumeControl.addEventListener("mousedown", this.onVolumeControlMouseDown.context(this), false);
    this.volumeControl.addEventListener("mouseup", this.onVolumeControlMouseUp.context(this), false);
    this.updateVolumeDisplay();

    this.onWindowResize = function(event) {
      this.positionController();
    }.context(this);
  },
  
  onMediaInfoRequest: function(response){
    try {
      var data = eval("("+response+")"); // assuming it's json there

      /*
        "title":    "someName",
        "duration": "318", // length in seconds
        "mime":     "video/webm; codecs='vp8, vorbis'", // or "audio/ogg"
        "source":   "http://url/id"
      */
      
      this.media.setAttribute('autoplay', this.options.autoplay ? 'true' : 'false');

      var src = document.createElement('source');
      src.setAttribute('type', data.mime);
      src.setAttribute('src',  data.source);

      this.media.appendChild(src);

    } catch(e) { alert('MediaJS could not process media info!'); } // TODO: PANIC （╯°□°）╯︵ ┻━┻
  },

  onLoadedMetadata: function(){
    if (this.media.videoWidth > 0 && this.media.videoHeight > 0 ){
      this.box.style.width = this.media.videoWidth + 'px';
      this.box.style.height = this.media.videoHeight + 'px';
    }
  },

  buildController: function(){

    /* Creating this HTML
      <div class="mjs-controls">
        <div class="mjs-play-control">
          <span></span>
        </div>
        <div class="mjs-progress-control">
          <div class="mjs-progress-holder">
            <div class="mjs-load-progress"></div>
            <div class="mjs-play-progress"></div>
          </div>
        </div>
        <div class="mjs-time-control">
          <span class="mjs-current-time-display">00:00</span><span> / </span><span class="mjs-duration-display">00:00</span>
        </div>
        <div class="mjs-volume-control">
          <div>
            <span></span><span></span><span></span><span></span><span></span><span></span>
          </div>
        </div>
        <div class="mjs-fullscreen-control">
          <div>
            <span></span><span></span><span></span><span></span>
          </div>
        </div>
      </div>
    */
    
    // Create a div to hold the different controls
    this.controls = _mjs_.createElement("div", { className: "mjs-controls" });

    // Build the play control
    this.playControl = _mjs_.createElement("div", { className: "mjs-play-control mjs-play", innerHTML: "<span></span>" });
    this.controls.appendChild(this.playControl);

    // Build the progress control
    this.progressControl = _mjs_.createElement("div", { className: "mjs-progress-control" });
    this.controls.appendChild(this.progressControl);

    // Create a holder for the progress bars
    this.progressHolder = _mjs_.createElement("div", { className: "mjs-progress-holder" });
    this.progressControl.appendChild(this.progressHolder);

    // Create the loading progress display
    this.loadProgress = _mjs_.createElement("div", { className: "mjs-load-progress" });
    this.progressHolder.appendChild(this.loadProgress);

    // Create the playing progress display
    this.playProgress = _mjs_.createElement("div", { className: "mjs-play-progress" });
    this.progressHolder.appendChild(this.playProgress);

    // Create the progress time display (00:00 / 00:00)
    this.timeControl = _mjs_.createElement("div", { className: "mjs-time-control" });
    this.controls.appendChild(this.timeControl);

    // Create the current play time display
    this.currentTimeDisplay = _mjs_.createElement("span", { className: "mjs-current-time-display", innerHTML: "00:00" });
    this.timeControl.appendChild(this.currentTimeDisplay);

    // Add time separator
    this.timeSeparator = _mjs_.createElement("span", { innerHTML: " / " });
    this.timeControl.appendChild(this.timeSeparator);

    // Create the total duration display
    this.durationDisplay = _mjs_.createElement("span", { className: "mjs-duration-display", innerHTML: "00:00" });
    this.timeControl.appendChild(this.durationDisplay);

    // Create the volumne control
    this.volumeControl = _mjs_.createElement("div", {
      className: "mjs-volume-control",
      innerHTML: "<div><span></span><span></span><span></span><span></span><span></span><span></span></div>"
    });
    this.controls.appendChild(this.volumeControl);

    this.volumeDisplay = this.volumeControl.children[0];

    // Crete the fullscreen control
    this.fullscreenControl = _mjs_.createElement("div", {
      className: "mjs-fullscreen-control",
      innerHTML: "<div><span></span><span></span><span></span><span></span></div>"
    });
    this.controls.appendChild(this.fullscreenControl);
    
    // Add the controls to the media's container
    this.box.appendChild(this.controls);
  },

  // Show the controller
  showController: function(){
    this.controls.style.display = "block";
    this.positionController();
  },

  // Place controller relative to the media's position
  positionController: function(){
    // Make sure the controls are visible
    if (this.controls.style.display == 'none') return;

    this.sizeProgressBar();
  },


  canPlaySource: function(){
    var children = this.media.children;
    for (var i=0; i<children.length; i++) {
      if (children[i].tagName.toUpperCase() == "SOURCE") {
        var canPlay = this.media.canPlayType(children[i].type);
        if(canPlay == "probably" || canPlay == "maybe") {
          return true;
        }
      }
    }
    return false;
  },

  // When the media is played
  onPlay: function(event){
    this.playControl.className = "mjs-play-control mjs-pause";
    this.trackPlayProgress();
  },

  // When the media is paused
  onPause: function(event){
    this.playControl.className = "mjs-play-control mjs-play";
    this.stopTrackingPlayProgress();
  },

  // When the media ends
  onEnded: function(event){
    this.media.pause();
    this.onPause();
  },

  onVolumeChange: function(event){
    this.updateVolumeDisplay();
  },

  onError: function(event){
    console.log(event);
    console.log(this.media.error);
  },

  onLoadedData: function(event){
    this.showController();
  },

  // When the media's load progress is updated
  // Does not work in all browsers (Safari/Chrome 5)
  onProgress: function(event){
    if(event.total > 0) {
      this.setLoadProgress(event.loaded / event.total);
    }
  },
  
  onMouseMove: function(event) {
    this.showControlBars();
    clearInterval(this.mouseMoveTimeout);
    this.mouseMoveTimeout = setTimeout(this.hideControlBars.context(this), 4000);
  },
  
  onMouseOut: function(event) {
     // Prevent flicker by making sure mouse hasn't left the video
    var parent = event.relatedTarget;
    while (parent && parent !== this.box) {
      parent = parent.parentNode;
    }
    if (parent !== this.box) {
      this.hideControlBars();
    }
  },
  
  showControlBars: function() {
    this.controls.style.display = 'block';
  },
  
  hideControlBars: function() {
    this.controls.style.display = 'none';
  },

  // Buffer watching method for load progress.
  // Used for browsers that don't support the progress event
  updateBufferedTotal: function(){
    if (this.media.buffered) {
      if (this.media.buffered.length >= 1) {
        this.setLoadProgress(this.media.buffered.end(0) / this.media.duration);
        if (this.media.buffered.end(0) == this.media.duration) {
          clearInterval(this.watchBuffer);
        }
      }
    } else {
      clearInterval(this.watchBuffer);
    }
  },

  setLoadProgress: function(percentAsDecimal){
    if (percentAsDecimal > this.percentLoaded) {
      this.percentLoaded = percentAsDecimal;
      this.updateLoadProgress();
    }
  },

  updateLoadProgress: function(){
    if (this.controls.style.display == 'none') return;
    this.loadProgress.style.width = (this.percentLoaded * (_mjs_.getComputedStyleValue(this.progressHolder, "width").replace("px", ""))) + "px";
  },

  // React to clicks on the play/pause button
  onPlayControlClick: function(event){
    if ((event.target == this.playControl || event.target.parentNode == this.playControl) || (event.target == this.box || event.target == this.media)) {
      if (this.media.preload !== 'auto')
        this.media.preload = 'auto';

      if (this.media.paused) {
        this.media.play();
      } else {
        this.media.pause();
      }
    
      event.stopPropagation();
    }
  },

  // Adjust the play position when the user drags on the progress bar
  onProgressHolderMouseDown: function(event){
    this.stopTrackingPlayProgress();

    if (this.media.paused) {
      this.mediaWasPlaying = false;
    } else {
      this.mediaWasPlaying = true;
      this.media.pause();
    }

    _mjs_.blockTextSelection();
    document.onmousemove = function(event) {
      this.setPlayProgressWithEvent(event);
    }.context(this);

    document.onmouseup = function(event) {
      _mjs_.unblockTextSelection();
      document.onmousemove = null;
      document.onmouseup = null;
      if (this.mediaWasPlaying) {
        this.media.play();
        this.trackPlayProgress();
      }
    }.context(this);
  },

  // When the user stops dragging on the progress bar, update play position
  // Backup for when the user only clicks and doesn't drag
  onProgressHolderMouseUp: function(event){
    this.setPlayProgressWithEvent(event);
  },

  // Adjust the volume when the user drags on the volume control
  onVolumeControlMouseDown: function(event){
    _mjs_.blockTextSelection();
    document.onmousemove = function(event) {
      this.setVolumeWithEvent(event);
    }.context(this);
    document.onmouseup = function() {
      _mjs_.unblockTextSelection();
      document.onmousemove = null;
      document.onmouseup = null;
    }.context(this);
  },

  // When the user stops dragging, set a new volume
  // Backup for when the user only clicks and doesn't drag
  onVolumeControlMouseUp: function(event){
    this.setVolumeWithEvent(event);
  },

  // Adjust the width of the progress bar to fill the controls width
  sizeProgressBar: function(){
    // this.progressControl.style.width =
    //   this.controls.offsetWidth 
    //   - this.playControl.offsetWidth
    //   - this.volumeControl.offsetWidth
    //   - this.timeControl.offsetWidth
    //   - this.fullscreenControl.offsetWidth
    //   - (this.getControlsPadding() * 6) 
    //   - this.getControlBorderAdjustment() 
    //   + "px";
    // this.progressHolder.style.width = (this.progressControl.offsetWidth - (this.timeControl.offsetWidth + 20)) + "px";
    this.updatePlayProgress();
    this.updateLoadProgress();
  },
  
  // Get the space between controls. For more flexible styling.
  getControlsPadding: function(){
    return _mjs_.findPosX(this.playControl) - _mjs_.findPosX(this.controls)
  },
  
  // When dynamically placing controls, if there are borders on the controls, it can break to a new line.
  getControlBorderAdjustment: function(){
    var leftBorder = parseInt(_mjs_.getComputedStyleValue(this.playControl, "border-left-width").replace("px", ""));
    var rightBorder = parseInt(_mjs_.getComputedStyleValue(this.playControl, "border-right-width").replace("px", ""));
    return leftBorder + rightBorder;
  },

  // Track & display the current play progress
  trackPlayProgress: function(){
    this.playProgressInterval = setInterval(function(){ this.updatePlayProgress(); }.context(this), 33);
  },

  // Turn off play progress tracking (when paused)
  stopTrackingPlayProgress: function(){
    clearInterval(this.playProgressInterval);
  },

  // Ajust the play progress bar's width based on the current play time
  updatePlayProgress: function(){
    if (this.controls.style.display == 'none') return;
    this.playProgress.style.width = ((this.media.currentTime / this.media.duration) * (_mjs_.getComputedStyleValue(this.progressHolder, "width").replace("px", ""))) + "px";
    this.updateTimeDisplay();
  },

  // Update the play position based on where the user clicked on the progresss bar
  setPlayProgress: function(newProgress){
    this.media.currentTime = newProgress * this.media.duration;
    this.playProgress.style.width = newProgress * (_mjs_.getComputedStyleValue(this.progressHolder, "width").replace("px", "")) + "px";
    this.updateTimeDisplay();
  },

  setPlayProgressWithEvent: function(event){
    var newProgress = _mjs_.getRelativePosition(event.pageX, this.progressHolder);
    this.setPlayProgress(newProgress);
  },

  // Update the displayed time (00:00)
  updateTimeDisplay: function(){
    this.currentTimeDisplay.innerHTML = _mjs_.formatTime(this.media.currentTime);
    if (this.media.duration) this.durationDisplay.innerHTML = _mjs_.formatTime(this.media.duration);
  },

  // Set a new volume based on where the user clicked on the volume control
  setVolume: function(newVol){
    this.media.volume = parseFloat(newVol);
    localStorage.volume = this.media.volume;
  },

  setVolumeWithEvent: function(event){
    var newVol = _mjs_.getRelativePosition(event.pageX, this.volumeControl.children[0]);
    this.setVolume(newVol);
  },

  // Update the volume control display
  // Unique to these default controls. Uses borders to create the look of bars.
  updateVolumeDisplay: function(){
    var volNum = Math.ceil(this.media.volume * 6);
    for(var i=0; i<6; i++) {
      if (i < volNum) {
        _mjs_.addClass(this.volumeDisplay.children[i], "mjs-volume-level-on")
      } else {
        _mjs_.removeClass(this.volumeDisplay.children[i], "mjs-volume-level-on");
      }
    }
  },
})


var mjs_waiting  = false;
var mjs_timer_id = 0;

var mjs_vocatube_url  = location.href.substr(0,location.href.lastIndexOf("/")+1);
var mjs_embed_name    = mjs_vocatube_url + 'embed.js';
var mjs_box_classname = 'mjs-box';


// Convenience Functions (mini library)
// Functions not specific to audio/video or MediaJS and could be replaced with a library like jQuery
var _mjs_ = {
  addClass: function(element, classToAdd){
    if (element.className.split(/\s+/).lastIndexOf(classToAdd) == -1) element.className = element.className == "" ? classToAdd : element.className + " " + classToAdd;
  },

  removeClass: function(element, classToRemove){
    if (element.className.indexOf(classToRemove) == -1) return;
    var classNames = element.className.split(/\s+/);
    classNames.splice(classNames.lastIndexOf(classToRemove),1);
    element.className = classNames.join(" ");
  },

  merge: function(obj1, obj2){
    for (attrname in obj2) { obj1[attrname] = obj2[attrname]; }
    return obj1;
  },

  createElement: function(tagName, attributes){
    return this.merge(document.createElement(tagName), attributes);
  },

  // Attempt to block the ability to select text while dragging controls
  blockTextSelection: function(){
    document.body.focus();
    document.onselectstart = function () { return false; };
  },

  // Turn off text selection blocking
  unblockTextSelection: function(){
    document.onselectstart = function () { return true; };
  },

  // Return seconds as MM:SS
  formatTime: function(seconds) {
    seconds = Math.round(seconds);
    minutes = Math.floor(seconds / 60);
    minutes = (minutes >= 10) ? minutes : "0" + minutes;
    seconds = Math.floor(seconds % 60);
    seconds = (seconds >= 10) ? seconds : "0" + seconds;
    return minutes + ":" + seconds;
  },

  // Return the relative horizonal position of an event as a value from 0-1
  getRelativePosition: function(x, relativeElement){
    return Math.max(0, Math.min(1, (x - this.findPosX(relativeElement)) / relativeElement.offsetWidth));
  },

  // Get an objects position on the page
  findPosX: function(obj) {
    var curleft = obj.offsetLeft;
    while(obj = obj.offsetParent) {
      curleft += obj.offsetLeft;
    }
    return curleft;
  },
  
  getComputedStyleValue: function(element, style){
    return window.getComputedStyle(element, null).getPropertyValue(style);
  },
  
  // implying CORS from server
  get: function(url, callback, domObject){
    if (typeof XMLHttpRequest == "undefined") throw new Error("This browser does not support XMLHttpRequest.");
 
    var request = new XMLHttpRequest();
    request.open("GET",url);

    request.onload = function() {
      if (request.readyState == 4 && request.status == 200) {
        callback(request.responseText);
      }
    }.context(this)
    request.send();
  },
  
  setupPlayers: function(){
    scripts = document.body.getElementsByTagName('script');
    var added = [];
    
    for (var i = 0; i < scripts.length; i++){
      var script = scripts[i];
      var scrBox = script.parentNode;

      if (script.src == mjs_embed_name && scrBox.className.indexOf(mjs_box_classname) != -1) {
        var options = {
          autoplay: scrBox.hasAttribute("autoplay"),
        };
        mjsPlayers.push(new MediaJS(scrBox, options));
        added.push(script);
      }
    }

    for (var j = 0; j < added.length; j++) added[j].parentNode.removeChild(added[j]);

    this.waiting = false;
  },
}


Function.prototype.context = function(obj) {
  var method = this
  
  temp = function() {
    return method.apply(obj, arguments)
  }

  return temp
}

//-------------------------------------------------------------------------------

function mjsSetup() {
  if (mjs_waiting) clearTimeout(mjs_timer_id);

  mjs_timer_id = setTimeout(_mjs_.setupPlayers, 100);
  mjs_waiting = true;
}
